package com.iphanta.community.following.service.impl;

import com.baidu.fsg.uid.UidGenerator;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.iphanta.community.common.BusinessException;
import com.iphanta.community.common.UserUtils;
import com.iphanta.community.common.entity.Page;
import com.iphanta.community.common.utils.*;
import com.iphanta.community.following.entity.*;
import com.iphanta.community.following.mapper.*;
import com.iphanta.community.following.service.DynamicService;
import com.iphanta.community.following.traffic.*;
import com.iphanta.community.following.traffic.vo.*;
import com.iphanta.community.mq.message.ActivityPost;
import com.iphanta.community.mq.message.DynamicFeed;
import com.iphanta.community.mq.message.Notification;
import com.iphanta.community.tags.service.TagsService;
import com.iphanta.community.tags.traffic.vo.TagInfo;
import com.iphanta.community.user.entity.UserBasicInfo;
import com.iphanta.community.user.mapper.RelationMapper;
import com.iphanta.community.user.mapper.UserBasicInfoMapper;
import com.iphanta.community.user.service.UserService;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RBitSet;
import org.redisson.api.RBucket;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author 墨声 E-mail: zehong.hongframe.huang@gmail.com
 * create time: 2/15/22 6:14 PM
 */
@Service
public class DynamicServiceImpl implements DynamicService {

    private static final Logger LOGGER = LoggerFactory.getLogger(DynamicServiceImpl.class);

    @Autowired
    private TagsService tagsService;

    @Autowired
    private DynamicMapper dynamicMapper;

    @Autowired
    private CommentsMapper commentsMapper;

    @Autowired
    private UserBasicInfoMapper userMapper;

    @Autowired
    private LikesMapper likesMapper;

    @Autowired
    private RelationMapper relationMapper;

    @Autowired
    private TagDynamicRelationMapper tagDynamicRelationMapper;

    @Resource
    private UidGenerator idGenerator;

    @Autowired
    private RedisTemplate<String, String> likedCounter;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    private static final String LIKED_SCRIPT =
            "if redis.call('HEXISTS', KEYS[1], KEYS[2]) == 1 then \n" +
                    "return 0 \n" +
                    "else \n" +
                    "redis.call('HSET', KEYS[1], KEYS[2], ARGV[1])\n" +
                    "redis.call('HINCRBY', KEYS[1], KEYS[3], ARGV[2])\n" +
                    "return 1\n" +
                    "end";

    private static final String HGETALL_FUNCTION =
            "local function hgetall(hash_key)\n" +
                    "local flat_map = redis.call('HGETALL', hash_key)\n" +
                    "local result = 'key='..hash_key\n" +
                    "for i = 1, #flat_map, 2 do\n" +
                    "result = result .. ','..flat_map[i]..'='..flat_map[i+1]..','\n" +
                    "end\n" +
                    "return result\n" +
                    "end\n";

    private static final String CURSOR_SCRIPT =
            "local cursor = redis.call('scan', KEYS[1], 'match', KEYS[2], 'count', ARGV[1])\n" +
                    "local hmap = {}\n" +
                    "local keys = cursor[2]\n" +
                    "for i in pairs(keys) do\n" +
                    "hmap[i] = hgetall(keys[i])\n" +
//            "redis.call('del', keys[i])\n" +
                    "end\n" +
                    "return hmap";

    @Override
    public PublishDynamicResponse publishDynamic(PublishDynamicRequest request) {
        Dynamic dynamic = new Dynamic();
        dynamic.setId(this.idGenerator.getUID());

        LOGGER.info("UID -> {}, sec: {}", this.idGenerator.parseUID(164221321561939969L), BitMapUtils.index(this.idGenerator.parseUID(164221321561939969L)));

        dynamic.setUid(UserUtils.getUid());
        List<String> res = new ArrayList<>();
        for (ResInfo resInfo : request.getResURIs()) {
            String s = resInfo.getUri() + "?" + "w=" + resInfo.getWidth() + "&h=" + resInfo.getHeight();
            res.add(s);
        }
        dynamic.setResJson(JsonUtils.INSTANCE.toJsonString(res));
        dynamic.setContext(request.getContext());
        dynamic.setDynamicType(0);
        dynamic.setMediaType(request.getMediaType());
        String tagCodes = String.join("|", request.getTags().stream()
                .map(e -> e.getCode()).collect(Collectors.toList()));
        dynamic.setTagCodes(tagCodes);
        dynamic.setIsDel(0);
        dynamic.setUpdateTime(new Date());
        dynamic.setCreateTime(new Date());
        dynamicMapper.insert(dynamic);

        DynamicFeed feed = new DynamicFeed();
        feed.setAuthorId(UserUtils.getUid());
        feed.setDynamicId(dynamic.getId());
        feed.setCreateTime(dynamic.getCreateTime());
        this.rocketMQTemplate.syncSend("dynamic-publish", new GenericMessage(feed));
        LOGGER.info("用户 {} 发布动态 {}, 已发至MQ", UserUtils.getUid(), dynamic.getId());

        if (Objects.nonNull(request.getActivityId())) {
            ActivityPost activityPost = new ActivityPost();
            activityPost.setActivityId(request.getActivityId());
            activityPost.setDynamicId(dynamic.getId());
            activityPost.setCreateTime(dynamic.getCreateTime());
            this.rocketMQTemplate.syncSend("photographic-post", new GenericMessage(activityPost));
            LOGGER.info("用户 {} 发布动态 {}, 已发至活动MQ", UserUtils.getUid(), dynamic.getId());

        }
        for (TagInfo tag : request.getTags()) {
            String invertedIndexKey = RedisKeyUtils.INSTANCE.key(RedisKey.LIST_INVERTED, tag.getCode());
            this.feedRedisTemplate.opsForList().leftPush(invertedIndexKey, dynamic.getId());
        }

        List<TagDynamicRelation> relations = request.getTags().stream().map(e -> {
            TagDynamicRelation relation = new TagDynamicRelation();
            relation.setTagCode(e.getCode());
            relation.setDynamicId(dynamic.getId());
            relation.setCreateTime(dynamic.getCreateTime());
            return relation;
        }).collect(Collectors.toList());
        this.tagDynamicRelationMapper.batchInsert(relations);
        PublishDynamicResponse response = new PublishDynamicResponse();
        response.setDynamicId(dynamic.getId());
        return response;
    }

    @Override
    public HomepageDynamicResponse homepageDynamic(HomepageDynamicRequest request) {
//        PageHelper.startPage(request.getPage(), 10);
        HomepageDynamicResponse response = new HomepageDynamicResponse();
        String uid = null;
        if (StringUtils.isNotBlank(request.getHostId())) {
            uid = request.getHostId();
        } else {
            uid = UserUtils.getUid();
        }
        List<Dynamic> dynamics = this.dynamicMapper.selectByUid(uid, request.getCursor());
        if (dynamics.size() < 11) {
            response.setCursor(-1L);
        } else {
            response.setCursor(dynamics.get(10).getId());
            dynamics.remove(10);
        }

        UserBasicInfo basicInfo = this.userMapper.selectByUid(uid);
        List<Droplet> flow = new ArrayList<>();
        for (Dynamic dynamic : dynamics) {
            Droplet droplet = new Droplet();
            BeanUtils.copyProperties(dynamic, droplet);
            droplet.setAuthorId(dynamic.getUid());
            droplet.setAuthorNickname(basicInfo.getNickname());
            droplet.setAuthorPhoto(basicInfo.getPhoto());
            droplet.setAuthorGameLevel(basicInfo.getGameLevel());
            droplet.setLiked(dynamic.getLiked() > 0);
            droplet.setRelation(-1);
            droplet.setTags(this.tagsService.tags(tagCodes(dynamic.getTagCodes())));
            flow.add(droplet);
        }

        response.setFlow(flow);
        return response;
    }

    private List<String> tagCodes(String s) {
        if (StringUtils.isBlank(s)) {
            return null;
        } else {
            return Arrays.asList(s.split("\\|"));

        }
    }

    @Autowired
    private UserBehaviorMapper userBehaviorMapper;

    @Autowired
    private FeedMapper feedMapper;

    @Autowired
    private RedisTemplate<String, Long> feedRedisTemplate;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public DynamicFlowResponse dynamicFlow(DynamicFlowRequest request) {
        Integer resolve = this.userBehaviorMapper.selectUnresolved(UserUtils.getUid());
        String feedsKey = RedisKeyUtils.INSTANCE.key(RedisKey.LIST_FEED, UserUtils.getUid());
        List<String> unfollowerIds = this.userBehaviorMapper.selectNewlyUnfollow(UserUtils.getUid());

        String active = this.likedCounter.opsForValue().get(RedisKeyUtils.INSTANCE.key(RedisKey.VALUE_ACTIVE, UserUtils.getUid()));

        if (resolve > 0 || StringUtils.isBlank(active)) {

            LOGGER.info("用户[{}]取关了{}", UserUtils.getUid(), unfollowerIds);
            List<Dynamic> newlyDynamic = this.dynamicMapper.selectNewlyDynamicsByFollowerId(UserUtils.getUid()); // TODO 大表查询

            if (Objects.nonNull(newlyDynamic)) {
                if (!CollectionUtils.isEmpty(newlyDynamic)) {
                    LOGGER.info("用户[{}], 新近动态[{} - {}], 共{}条动态", UserUtils.getUid(), newlyDynamic.get(0).getId(), newlyDynamic.get(newlyDynamic.size() - 1).getId(), newlyDynamic.size());
                } else {
                    LOGGER.info("用户[{}]暂无新动态", UserUtils.getUid());
                }

                List<Long> oldlyFeeds = this.feedMapper.selectDynamicIdByFollowerId(UserUtils.getUid(), unfollowerIds);
                if (!CollectionUtils.isEmpty(oldlyFeeds)) {
                    LOGGER.info("用户[{}], 较老的动态[{} - {}], 共{}条动态", UserUtils.getUid(), oldlyFeeds.get(0), oldlyFeeds.get(oldlyFeeds.size() - 1), oldlyFeeds.size());
                } else {
                    LOGGER.info("用户[{}]暂无动态", UserUtils.getUid());
                }

                newlyDynamic.removeIf(dynamic -> oldlyFeeds.contains(dynamic.getId()));
                List<Feed> newlyFeeds = newlyDynamic.stream().map(dynamic -> {
                    Feed feed = new Feed();
                    feed.setId(this.idGenerator.getUID());
                    feed.setDynamicId(dynamic.getId());
                    feed.setFollowingId(dynamic.getUid());
                    feed.setFollowerId(UserUtils.getUid());
                    feed.setDynamicCreateTime(dynamic.getCreateTime());
                    oldlyFeeds.add(dynamic.getId());
                    return feed;
                }).collect(Collectors.toList());
                LOGGER.info("共{}条新动态", newlyFeeds.size());
                oldlyFeeds.sort((o2, o1) -> {
                    long c = o2 - o1;
                    return (c > 0 ? 1 : -1);
                });
                if (!CollectionUtils.isEmpty(newlyFeeds)) {
                    this.feedMapper.batchInsert(newlyFeeds);
                } else {
                    LOGGER.info("用户[{}]无需更新Feeds", UserUtils.getUid());
                }

                this.feedRedisTemplate.execute(new SessionCallback<Integer>() {
                    @Override
                    public Integer execute(RedisOperations operations) throws DataAccessException {
                        operations.multi();
                        operations.delete(feedsKey);
                        if (!CollectionUtils.isEmpty(oldlyFeeds)) {
                            LOGGER.info("动态[{} - {}]", oldlyFeeds.get(0), oldlyFeeds.get(oldlyFeeds.size() - 1));
                            operations.opsForList().leftPushAll(feedsKey, oldlyFeeds);
                        } else {
                            operations.opsForList().leftPush(feedsKey, -1);
                        }
                        operations.expire(feedsKey, 16, TimeUnit.HOURS);
                        List result = operations.exec();
                        return result.size();
                    }
                });

                this.userBehaviorMapper.resolve(UserUtils.getUid());

            }
        }


        // 查Redis
        if (!this.feedRedisTemplate.hasKey(feedsKey)) {
            List<Long> dynamicID = this.feedMapper.selectDynamicIdByFollowerId(UserUtils.getUid(), unfollowerIds);
            if (!CollectionUtils.isEmpty(dynamicID)) {
                this.feedRedisTemplate.opsForList().leftPushAll(feedsKey, dynamicID);
            }

        }

        DynamicFlowResponse response = new DynamicFlowResponse();
        List<Long> ids = null;
        if (Objects.equals(request.getCursor(), 0L)) {
            ids = this.feedRedisTemplate.opsForList().range(feedsKey, 0L, 11L);
        } else if (Objects.equals(request.getCursor(), -1L)) {
            ids = new ArrayList<>();
        } else {
            Long index = this.feedRedisTemplate.opsForList().lastIndexOf(feedsKey, request.getCursor());
            if (Objects.isNull(index)) {
                throw new BusinessException("游标异常", ResultUtils.FALL_ON_PARAMS.result());
            }
            ids = this.feedRedisTemplate.opsForList().range(feedsKey, index, index + 11);
        }
        if (ids.size() < 11) {
            //TODO 先去查MySQL
            response.setCursor(-1L);
        } else {
            response.setCursor(ids.get(10));
            ids.remove(10);
        }

        List<Droplet> flow = new ArrayList<>();
        if (!CollectionUtils.isEmpty(ids)) {
//            List<Dynamic> dynamics = this.dynamicMapper.selectByIds(UserUtils.getUid(), ids);
            flow = this.query(ids);
        }

        response.setFlow(flow);
        return response;
    }

    @Override
    public DynamicFlowResponse squareFlow(DynamicFlowRequest request) {
        List<Dynamic> dynamics = this.dynamicMapper.select(UserUtils.getUid(), request.getCursor());
        DynamicFlowResponse response = new DynamicFlowResponse();
        if (dynamics.size() < 11) {
            response.setCursor(-1L);
        } else {
            Dynamic last = dynamics.remove(10);
            response.setCursor(last.getId());
        }
        response.setFlow(this.dynamic2Flow(dynamics));
        return response;
    }

    @Override
    public TagDynamicResponse tagDynamicFlow(TagDynamicRequest request) {
        String invertedIndexKey = RedisKeyUtils.INSTANCE.key(RedisKey.LIST_INVERTED, request.getTagCode());
        TagDynamicResponse response = new TagDynamicResponse();
        List<Long> ids = null;
        if (Objects.equals(request.getCursor(), 0L)) {
            ids = this.feedRedisTemplate.opsForList().range(invertedIndexKey, 0L, 11L);
        } else if (Objects.equals(request.getCursor(), -1L)) {
            ids = new ArrayList<>();
        } else {
            Long index = this.feedRedisTemplate.opsForList().lastIndexOf(invertedIndexKey, request.getCursor());
            if (Objects.isNull(index)) {
                throw new BusinessException("游标异常", ResultUtils.FALL_ON_PARAMS.result());
            }
            ids = this.feedRedisTemplate.opsForList().range(invertedIndexKey, index, index + 11);
        }
        if (ids.size() < 11) {
            //TODO 先去查MySQL
            response.setCursor(-1L);
        } else {
            response.setCursor(ids.get(10));
            ids.remove(10);
        }

        List<Droplet> flow = new ArrayList<>();
        if (!CollectionUtils.isEmpty(ids)) {
//            List<Dynamic> dynamics = this.dynamicMapper.selectByIds(UserUtils.getUid(), ids);
            flow = this.query(ids);
        }

        response.setFlow(flow);
        return response;
    }

    @Override
    public PublishCommentResponse publishComment(PublishCommentRequest request) {
        Dynamic dynamic = this.dynamicMapper.selectById(request.getDynamicId());
        if (Objects.isNull(dynamic)) {
            throw new BusinessException("动态不存在", ResultUtils.FAIL_ON_BUSINESS.result());
        }
        Comments comments = new Comments();
        comments.setId(this.idGenerator.getUID());
        comments.setComment(request.getComment());
        comments.setUid(UserUtils.getUid());
        comments.setDynamicId(request.getDynamicId());
        comments.setCreateTime(request.getCreateTime());
        this.commentsMapper.insert(comments);
        this.dynamicMapper.updateCommentCount(request.getDynamicId());

        if (!Objects.equals(request.getAuthorId(), UserUtils.getUid())) {
            Notification notification = new Notification();
            notification.setByUser(UserUtils.getUid());
            notification.setUid(request.getAuthorId());
            notification.setMissId(request.getDynamicId());
            notification.setType("comment");
            notification.setContent(request.getComment());
            notification.setCreateTime(new Date());
            this.rocketMQTemplate.syncSend("user-notifications", new GenericMessage(notification));
        }

        PublishCommentResponse response = new PublishCommentResponse();
        response.setCommentId(comments.getId());
        return response;
    }

    @Override
    public LikeDynamicResponse likeDynamic(LikeDynamicRequest request) {

        String likedKey = RedisKeyUtils.INSTANCE.key(RedisKey.HASH_LIKED,
                request.getDynamicId().toString());

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(Long.class);
        redisScript.setScriptText(LIKED_SCRIPT);
        Long result = this.feedRedisTemplate.execute(redisScript,
                Arrays.asList(likedKey, UserUtils.getUid(), "count"), 0, 1);

        if (Objects.equals(result, 1L) && !Objects.equals(request.getAuthorId(), UserUtils.getUid())) {
            Notification notification = new Notification();
            notification.setByUser(UserUtils.getUid());
            notification.setUid(request.getAuthorId());
            notification.setMissId(request.getDynamicId());
            notification.setType("like");
            notification.setCreateTime(new Date());
            this.rocketMQTemplate.syncSend("user-notifications", new GenericMessage(notification));
        }

        LikeDynamicResponse response = new LikeDynamicResponse();
        response.setLiked(Boolean.TRUE);

        setLiked(request.getDynamicId());
        return response;
    }


    @XxlJob("syncLiked")
    @Transactional(rollbackFor = Exception.class)
    public ReturnT<String> syncLiked(String param) {
        List<Like> likes = new ArrayList<>();
        List<Dynamic> dynamics = new ArrayList<>();
        List<String> keys = new ArrayList<>();

        DefaultRedisScript<List> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(List.class);
        redisScript.setScriptText(HGETALL_FUNCTION + CURSOR_SCRIPT);

        List hmaps = this.likedCounter.execute(redisScript, Arrays.asList("0", RedisKey.CURSOR_MATCH_LIKED), "100");
        if (CollectionUtils.isEmpty(hmaps)) {
            return ReturnT.SUCCESS;
        }

        for (String s : (List<String>) hmaps) {
            String[] entries = s.split(",");

            Dynamic dynamic = new Dynamic();
            Date createTime = new Date();
            for (String entry : entries) {
                if (StringUtils.isBlank(entry)) {
                    continue;
                }
                String k = entry.split("=")[0];
                String v = entry.split("=")[1];
                if (Objects.equals(k, "key")) {
                    keys.add(v);
                    Long dynamicId = Long.valueOf(v.split(":")[1]);
                    dynamic.setId(dynamicId);
                } else if (Objects.equals(k, "count")) {
                    Long count = Long.valueOf(v);

                    dynamic.setLikedCount(count);
                } else {
                    Like like = new Like();
                    like.setDynamicId(dynamic.getId());
                    like.setLikedByUid(k);
                    like.setIsDel(0);
                    like.setCreateTime(createTime);
                    likes.add(like);
                }
            }
            dynamics.add(dynamic);
        }
        likes.stream().forEach(like -> like.setId(this.idGenerator.getUID()));
        LOGGER.info("{} \n{}", likes, dynamics);
        try {
            if (!CollectionUtils.isEmpty(likes)) {
                this.likesMapper.batchInsert(likes);
            }
            if (!CollectionUtils.isEmpty(dynamics)) {
                this.dynamicMapper.batchUpdateLikedCount(dynamics);
            }
            if (!CollectionUtils.isEmpty(keys)) {
                this.likedCounter.delete(keys);
            }

        } catch (Exception e) {
            LOGGER.error("", e);
            throw e;
        }

        return ReturnT.SUCCESS;
    }

    @Override
    public DynamicCommentsResponse comments(DynamicCommentsRequest request) {
        DynamicCommentsResponse response = new DynamicCommentsResponse();
        List<DynamicComment> dynamicComments = new ArrayList<>();

        List<Comments> comments = this.commentsMapper.selectCommentsByDynamicId(request.getDynamicId(), request.getCursor());
        if (comments.size() > 15) {
            response.setCursor(comments.remove(comments.size() - 1).getId());
        } else {
            response.setCursor(-1L);
        }

        for (Comments item : comments) {
            DynamicComment dynamicComment = new DynamicComment();
            BeanUtils.copyProperties(item, dynamicComment);
            dynamicComment.setAuthorId(item.getUid());
            UserBasicInfo user = this.userMapper.selectByUid(item.getUid());
            dynamicComment.setAuthorName(user.getNickname());
            dynamicComment.setAuthorPhoto(user.getPhoto());
            dynamicComments.add(dynamicComment);
        }
        response.setTotal(this.commentsMapper.count(request.getDynamicId()));
        response.setComments(dynamicComments);
        return response;
    }

    @Override
    public DynamicLikeResponse likes(DynamicLikeRequest request) {
        DynamicLikeResponse response = new DynamicLikeResponse();
        List<DynamicLike> dynamicLikes = new ArrayList<>();
        PageHelper.startPage(request.getPage(), 15);
        List<Like> likes = this.likesMapper.selectLikesByDynamicId(request.getDynamicId());
        PageInfo<Like> pageInfo = new PageInfo<>(likes);

        for (Like item : likes) {
            DynamicLike dynamicLike = new DynamicLike();
            BeanUtils.copyProperties(item, dynamicLike);
            dynamicLike.setAuthorId(item.getLikedByUid());
            UserBasicInfo user = this.userMapper.selectByUid(item.getLikedByUid());
            dynamicLike.setAuthorName(user.getNickname());
            dynamicLike.setAuthorPhoto(user.getPhoto());
            dynamicLikes.add(dynamicLike);
        }
        Page<DynamicLike> page = new Page<>();
        page.setPageNum(pageInfo.getPageNum());
        page.setPageSize(pageInfo.getPageSize());
        page.setTotal(pageInfo.getTotal());
        response.setLikes(page);
        return response;
    }

    @Override
    public RetractionResponse retract(RetractionRequest request) {
        RetractionResponse response = new RetractionResponse();
        if (Objects.equals(request.getOption(), "dynamic")) {
            this.dynamicMapper.deleteDynamic(request.getDynamicId());
            response.setSuccess(Boolean.TRUE);
        } else if (Objects.equals(request.getOption(), "liked")) {
            String likedKey = RedisKeyUtils.INSTANCE.key(RedisKey.HASH_LIKED,
                    request.getDynamicId().toString());
            Integer c = this.likesMapper.unlike(UserUtils.getUid(), request.getDynamicId());
            if (c > 0) {
                this.likedCounter.opsForHash().increment(likedKey, "count", -1);
            }

//            String unlikeKey = RedisKeyUtils.INSTANCE.key("unlike",
//                    request.getId().toString());
//            this.likedCounter.opsForValue().set(unlikeKey, "1");
            response.setSuccess(Boolean.TRUE);
        } else if (Objects.equals(request.getOption(), "comments")) {
            this.commentsMapper.deleteComments(request.getCommentId());
        } else {
            response.setSuccess(Boolean.FALSE);
        }
        return response;
    }

    @Autowired
    private UserService userService;

    @Override
    public List<Droplet> dynamic2Flow(List<Dynamic> dynamics) {
        List<Droplet> flow = new ArrayList<>();
        dynamics.stream().forEach(dynamic -> {
            Droplet droplet = new Droplet();
            BeanUtils.copyProperties(dynamic, droplet);
            droplet.setAuthorId(dynamic.getUid());
            {
                UserSimpleInfo simpleInfo = this.userService.simpleInfo(dynamic.getUid());
                droplet.setAuthorNickname(simpleInfo.getNickname());
                droplet.setAuthorPhoto(simpleInfo.getPhoto());
                droplet.setAuthorGameLevel(simpleInfo.getGameLevel());
                Integer relation = this.relationMapper.relation(UserUtils.getUid(), dynamic.getUid());

                if (Objects.equals(UserUtils.getUid(), dynamic.getUid())) {
                    droplet.setRelation(-1);
                } else {
                    if (Objects.isNull(relation)) {
                        relation = 0;
                    }
                    droplet.setRelation(relation);
                }
            }
            if (Objects.nonNull(dynamic.getLiked())) {
                droplet.setLiked(dynamic.getLiked() > 0);
            }
            droplet.setTags(this.tagsService.tags(tagCodes(dynamic.getTagCodes())));
            flow.add(droplet);
        });
        return flow;
    }

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public List<Droplet> query(List<Long> dynamicIds) {
        List<Droplet> result = new ArrayList<>();

        List<String> dynamicIdStr = dynamicIds.stream().map(e -> RedisKeyUtils.INSTANCE.key("dynamic", e.toString())).collect(Collectors.toList());
        String[] arr = new String[dynamicIdStr.size()];
        dynamicIdStr.toArray(arr);

        Map<String, Droplet> dropletMap = this.redissonClient.getBuckets().get(arr);

        if (!CollectionUtils.isEmpty(dropletMap)) {
            dropletMap.forEach((k, v) -> {
//                Integer liked = this.dynamicMapper.liked(UserUtils.getUid(), v.getId());
                v.setLiked(getLiked(v.getId()));
                result.add(v);
                dynamicIds.remove(v.getId());
            });
        }

        if (!CollectionUtils.isEmpty(dynamicIds)) {
            List<Dynamic> dynamics = this.dynamicMapper.selectInIdIgnoreDel(dynamicIds);
            List<Droplet> _remain = dynamic2Flow(dynamics);

            _remain.forEach(e -> {
                RBucket<Droplet> newlyCache = this.redissonClient.getBucket(RedisKeyUtils.INSTANCE.key("dynamic", e.getId().toString()));
                newlyCache.set(e, 18, TimeUnit.HOURS);

//                Integer liked = this.dynamicMapper.liked(UserUtils.getUid(), e.getId());
                e.setLiked(getLiked(e.getId()));
            });
//            this.redissonClient.getBuckets().set(newlyCache);
            result.addAll(_remain);
        }

        return result;
    }

    @Autowired
    private RedisTemplate<String, String> dynamicInfoRedis;

    @Override
    public Map<Long, String> dynamicResJson(List<Long> dynamicIds) {
        List<String> keys = dynamicIds.stream().map(p ->
                RedisKeyUtils.INSTANCE.key(RedisKey.VALUE_DYNAMIC_INFO, p.toString()))
                .collect(Collectors.toList());
        List<String> resJsons = this.dynamicInfoRedis.opsForValue().multiGet(keys);
        Map<Long, String> result = new HashMap<>();
        if (!CollectionUtils.isEmpty(resJsons)) {
            for (String i : resJsons) {
                if (StringUtils.isBlank(i)) {
                    continue;
                }
                String dynamic = i.split("###")[0];
                String resJson = i.split("###")[1];
                result.put(Long.valueOf(dynamic), resJson);
                dynamicIds.remove(Long.valueOf(dynamic));
            }
        }
        if (!CollectionUtils.isEmpty(dynamicIds)) {
            List<Dynamic> dynamics = this.dynamicMapper.selectInIdIgnoreDel(dynamicIds);
            dynamics.forEach(item -> {
                result.put(item.getId(), item.getResJson());
                this.dynamicInfoRedis.opsForValue().set(RedisKeyUtils.INSTANCE.key(RedisKey.VALUE_DYNAMIC_INFO, item.getId().toString()),
                        item.getId() + "###" + item.getResJson(),
                        72, TimeUnit.HOURS);
            });
        }
        return result;
    }

    @Override
    public Long sumLikedCount(String authorId) {
        return this.dynamicMapper.sumLikedCount(authorId);
    }

    private void setLiked(long dynamicId) {
        String parse = this.idGenerator.parseUID(dynamicId);
        String key = RedisKeyUtils.INSTANCE.key("hash-liked", BitMapUtils.date(parse), UserUtils.getUid());
        RMap<String, Integer> hash = this.redissonClient.getMap(key);
        hash.put(String.valueOf(BitMapUtils.index(parse)), 1);
    }

    private Boolean getLiked(long dynamicId) {
        String parse = this.idGenerator.parseUID(dynamicId);
        String key = RedisKeyUtils.INSTANCE.key("hash-liked", BitMapUtils.date(parse), UserUtils.getUid());
        RMap<String, Integer> hash = this.redissonClient.getMap(key);
        return Objects.equals(hash.get(String.valueOf(BitMapUtils.index(parse))), 1);
    }


}
